﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IdentityModel.Metadata;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Xml;

namespace ZDV.IdentityModel.Tools
{
    public class Metadata
    {
        private readonly MetadataBase _metadataBase;
        private readonly SecurityTokenServiceDescriptor _stsDescriptor;

        public String IssuerName { get; private set; }

        public Metadata(String federationMetadataLocation)
        {
            var metadataSerializer = new MetadataSerializer();

            using (var xmlDictionaryReader =
                XmlDictionaryReader.CreateDictionaryReader(new XmlTextReader(federationMetadataLocation)))
            {
                _metadataBase = metadataSerializer.ReadMetadata(xmlDictionaryReader);
            }

            var entitiesDescriptor = _metadataBase as EntitiesDescriptor;
            if (entitiesDescriptor != null)
            {
                foreach (var childEntityDescriptor in entitiesDescriptor.ChildEntities)
                    foreach (var stsDescriptor in childEntityDescriptor.RoleDescriptors.OfType<SecurityTokenServiceDescriptor>())
                    {
                        IssuerName = childEntityDescriptor.EntityId.Id;
                        _stsDescriptor = stsDescriptor;
                        break;
                    }
            }

            var entityDescriptor = _metadataBase as EntityDescriptor;
            if (entityDescriptor != null)
            {
                IssuerName = entityDescriptor.EntityId.Id;
                foreach (var stsDescriptor in entityDescriptor.RoleDescriptors
                    .OfType<SecurityTokenServiceDescriptor>())
                {
                    _stsDescriptor = stsDescriptor;
                }
            }

            if (_stsDescriptor == null)
            {
                throw new MetadataNotReadableException("The metadata does not contain an STSDescriptor.");
            }
        }

        private List<X509Certificate2> _securityTokenServiceSigningCertificates;
        public IEnumerable<X509Certificate2> SecurityTokenServiceSigningCertificates
        {
            get
            {
                lock (this)
                {
                    if (_securityTokenServiceSigningCertificates == null)
                    {
                        _securityTokenServiceSigningCertificates = new List<X509Certificate2>();
                        
                        if (_stsDescriptor != null && _stsDescriptor.Keys != null)
                        {
                            var keyDescriptors = _stsDescriptor.Keys as Collection<KeyDescriptor>;
                            if (keyDescriptors != null && keyDescriptors.Any())
                            {
                                foreach (var keyInfo in keyDescriptors
                                    .Where(
                                        kd =>
                                        kd.KeyInfo != null &&
                                        (kd.Use == KeyType.Signing || kd.Use == KeyType.Unspecified))
                                    .Select(kd => kd.KeyInfo))
                                {
                                    X509RawDataKeyIdentifierClause x509RawDataKeyIdentifierClause;
                                    keyInfo.TryFind(out x509RawDataKeyIdentifierClause);
                                    if (x509RawDataKeyIdentifierClause != null)
                                    {
                                        var certificate =
                                            new X509Certificate2(x509RawDataKeyIdentifierClause.GetX509RawData());
                                        _securityTokenServiceSigningCertificates.Add(certificate);
                                    }
                                }
                            }
                        }
                    }
                }

                if (!_securityTokenServiceSigningCertificates.Any())
                {
                    throw new NoCertificateInMetadataException("There were no signing or unspecified certificates in the metadata.");
                }
                return _securityTokenServiceSigningCertificates;
            }
        }
    }
}
